Go 字符串拼接6种,最快的方式 |
您所在的位置:网站首页 › go string比较 › Go 字符串拼接6种,最快的方式 |
我们首先来了解一下Go语言中string类型的结构定义,先来看一下官方定义: // string is the set of all strings of 8-bit bytes, conventionally but not// necessarily representing UTF-8-encoded text. A string may be empty, but// not nil. Values of string type are immutable.type string stringstring是一个8位字节的集合,通常但不一定代表UTF-8编码的文本。string可以为空,但是不能为nil。string的值是不能改变的。 string类型本质也是一个结构体,定义如下: type stringStruct struct { str unsafe.Pointer len int}stringStruct和slice还是很相似的,str指针指向的是某个数组的首地址,len代表的就是数组长度。怎么和slice这么相似,底层指向的也是数组,是什么数组呢?我们看看他在实例化时调用的方法: //go:nosplitfunc gostringnocopy(str *byte) string { ss := stringStruct{str: unsafe.Pointer(str), len: findnull(str)} s := *(*string)(unsafe.Pointer(&ss)) return s}入参是一个byte类型的指针,从这我们可以看出string类型底层是一个byte类型的数组,所以我们可以画出这样一个图片:
string类型本质上就是一个byte类型的数组,在Go语言中string类型被设计为不可变的,不仅是在Go语言,其他语言中string类型也是被设计为不可变的,这样的好处就是:在并发场景下,我们可以在不加锁的控制下,多次使用同一字符串,在保证高效共享的情况下而不用担心安全问题。 string类型虽然是不能更改的,但是可以被替换,因为stringStruct中的str指针是可以改变的,只是指针指向的内容是不可以改变的,也就说每一个更改字符串,就需要重新分配一次内存,之前分配的空间会被gc回收。 关于string类型的知识点就描述这么多,方便我们后面分析字符串拼接。 字符串拼接的6种方式及原理 原生拼接方式"+"Go语言原生支持使用+操作符直接对两个字符串进行拼接,使用例子如下: var s strings += "asong"s += "真帅"这种方式使用起来最简单,基本所有语言都有提供这种方式,使用+操作符进行拼接时,会对字符串进行遍历,计算并开辟一个新的空间来存储原来的两个字符串。 字符串格式化函数fmt.SprintfGo语言中默认使用函数fmt.Sprintf进行字符串格式化,所以也可使用这种方式进行字符串拼接: str := "asong"str = fmt.Sprintf("%s%s", str, str)fmt.Sprintf实现原理主要是使用到了反射,具体源码分析因为篇幅的原因就不在这里详细分析了,看到反射,就会产生性能的损耗,你们懂得!!! Strings.builderGo语言提供了一个专门操作字符串的库strings,使用strings.Builder可以进行字符串拼接,提供了writeString方法拼接字符串,使用方式如下: var builder strings.Builderbuilder.WriteString("asong")builder.String()strings.builder的实现原理很简单,结构如下: type Builder struct { addr *Builder // of receiver, to detect copies by value buf []byte // 1}addr字段主要是做copycheck,buf字段是一个byte类型的切片,这个就是用来存放字符串内容的,提供的writeString()方法就是像切片buf中追加数据: func (b *Builder) WriteString(s string) (int, error) { b.copyCheck() b.buf = append(b.buf, s...) return len(s), nil}提供的String方法就是将[]]byte转换为string类型,这里为了避免内存拷贝的问题,使用了强制转换来避免内存拷贝: func (b *Builder) String() string { return *(*string)(unsafe.Pointer(&b.buf))} bytes.Buffer因为string类型底层就是一个byte数组,所以我们就可以Go语言的bytes.Buffer进行字符串拼接。bytes.Buffer是一个一个缓冲byte类型的缓冲器,这个缓冲器里存放着都是byte。使用方式如下: buf := new(bytes.Buffer)buf.WriteString("asong")buf.String()bytes.buffer底层也是一个[]byte切片,结构体如下: type Buffer struct { buf []byte // contents are the bytes buf[off : len(buf)] off int // read at &buf[off], write at &buf[len(buf)] lastRead readOp // last read operation, so that Unread* can work correctly.}因为bytes.Buffer可以持续向Buffer尾部写入数据,从Buffer头部读取数据,所以off字段用来记录读取位置,再利用切片的cap特性来知道写入位置,这个不是本次的重点,重点看一下WriteString方法是如何拼接字符串的: func (b *Buffer) WriteString(s string) (n int, err error) { b.lastRead = opInvalid m, ok := b.tryGrowByReslice(len(s)) if !ok { m = b.grow(len(s)) } return copy(b.buf[m:], s), nil}切片在创建时并不会申请内存块,只有在往里写数据时才会申请,首次申请的大小即为写入数据的大小。如果写入的数据小于64字节,则按64字节申请。采用动态扩展slice的机制,字符串追加采用copy的方式将追加的部分拷贝到尾部,copy是内置的拷贝函数,可以减少内存分配。 但是在将[]byte转换为string类型依旧使用了标准类型,所以会发生内存分配: func (b *Buffer) String() string { if b == nil { // Special case, useful in debugging. return "" } return string(b.buf[b.off:])} strings.joinStrings.join方法可以将一个string类型的切片拼接成一个字符串,可以定义连接操作符,使用如下: baseSlice := []string{"asong", "真帅"}strings.Join(baseSlice, "")strings.join也是基于strings.builder来实现的,代码如下: func Join(elems []string, sep string) string { switch len(elems) { case 0: return "" case 1: return elems[0] } n := len(sep) * (len(elems) - 1) for i := 0; i `[]byte`转换`string` > "+" > `fmt.sprintf 总结本文我们针对6种字符串的拼接方式进行介绍,并通过benckmark对比了效率,无论什么时候使用strings.builder都不会错,但是在少量字符串拼接时,直接+也就是更优的方式,具体业务场景具体分析,不要一概而论 |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |